﻿package de.gameduell.framework.game.billard.model {
	import de.gameduell.framework.debug.Debug;
	import de.gameduell.framework.game.billard.controller.BillardEngine;
	import de.gameduell.framework.game.billard.controller.HoleEngine;
	import de.gameduell.framework.game.billard.event.SettingEvent;
	
	import org.cove.ape.APEngine;
	import org.cove.ape.AbstractParticle;
	import org.cove.ape.Clonable;
	import org.cove.ape.RectangleParticle;
	import org.cove.ape.SphereParticle;
	import org.cove.ape.Vector;
	
	import flash.display.Sprite;
	import flash.events.EventDispatcher;	

	/**
	 * @author cbra
	 */
	public class Ball extends SphereParticle implements Clonable{
				
		private var _animationPath:Array;
		private var _step:AnimationStep;
		private var eventDispatcher:EventDispatcher;
		private var _ballSprite:Sprite;
		private var _hole:Hole;
		private var _removed:Boolean;
		private var _id:int;
		private var _fallen:Boolean;
		private var settings:BillardSettings;
		private var _holeId:int;
		private var _lastStepIndex:int;
		private var _ballCollisions:int;
		private var _cushionCollisions:int;
		private var _collisionVelocityMagnitude:Number;
		private var _firstBallTouched:Ball;
		private var _touchedCushion:Boolean;


		public function Ball(aSettings:BillardSettings,apEngine:BillardEngine,aSprite:Sprite,aId:int) {
			super(apEngine,aSprite.x, aSprite.y,aSprite.width/2);	
			_ballSprite = aSprite;	
//			billardEngine = apEngine;
			_animationPath = new Array();	
			eventDispatcher = new EventDispatcher();
			_hole = null;
			_firstBallTouched = null;
			_id = aId;
			_holeId = -1;
			_lastStepIndex = 0;
			_ballCollisions = 0;
			_cushionCollisions = 0;
			_collisionVelocityMagnitude = 0;
			settings = aSettings;
			settings.addEventListener(SettingEvent.CHANGED, settingChanged);
			settings.addEventListener(SettingEvent.LOADED, refreshSettings);
			apEngine.addBall(this);
			createStep();
			isStopped();
		}
		
		public function get billardSettings():BillardSettings {	
			return settings;
		}
		
		public function set apEngine(aEngine:BillardEngine):void{
			_apEngine = aEngine;
		}
		
		public function get apEngine():BillardEngine{
			return BillardEngine(_apEngine);
		}
		
		override public function clone(aEngine:APEngine):Clonable {
			var clone:Ball = new Ball(settings,BillardEngine(aEngine),_ballSprite,id);
			clone.copySphere(this);
			return clone;	
		}

		public function addToHole(holeEngine:HoleEngine):void {
			apEngine = holeEngine;
			collidable = true;
			fixed = false;
			holeEngine.addBall(this);
			holeEngine.calcHoleBallPositions();
		}
		
		public function removeFromHole(aApEngine:BillardEngine):void {
			// _apEngine is the hole engine
			BillardEngine(_apEngine).removeBallById(id);
			apEngine = aApEngine;
			
			_holeId = hole.id;
			
			hole = null;
			
			collisionLastMajorFrame = false;
			collisionLastMinorFrame = false;
		
			_collisionPosition = null;
			_collisionVelocity = null;
			collidedParticles.length = 0;
			
			velocity = new Vector();
			createStep();
		}
		
		public function bringBackToTable():void {		
			collidable = true;
			fixed = false;
			fallen = false;
			removed = false;
		}

		public function get removed():Boolean{
			return _removed;
		}
		
		public function set removed(aRemoved:Boolean):void{
			 _removed = aRemoved;
		}
		
		public function set fallen(isFallen:Boolean):void{
			_fallen = isFallen;
		}
		
		public function set hole(aHole:Hole):void{
			_hole = aHole;
		}
		
		public function get fallen():Boolean{
			return _fallen;
		}
		
		public function get id():int{
			return _id;
		}
		
		public function get hole():Hole{
			return _hole;
		}
		
		public function get holeId():int{
			return _holeId;
		}
		
		public function toString():String{
			return " collidable: "+collidable+" removed: "+_removed + " fallen: "+ _fallen + " isStopped: "+isStopped() + " collidedParticles.length: "+collidedParticles.length+" position: "+ position + " effet: " + spin ;
		}

		/**********************************************
		 * 				ANIMATIONPATH				  *
		 **********************************************/
		
		public function synchronize():void{
			createStep();
		}
		
		public function createStep():void {
			_step = new AnimationStep(this);
			_animationPath.push(_step);
		}
		
		public function get step():AnimationStep{
			if(animationLength == 0){
				return new AnimationStep(this);
			}
			return _step;
		}
		
		public function animate(positionIndex:int):void {
			if(positionIndex > _animationPath.length - 1) {
				_step = new AnimationStep(this);
//				_step = AnimationStep(_animationPath[_animationPath.length - 1]);
			}else {
				_step = AnimationStep(_animationPath[positionIndex]);
				checkLostSteps(positionIndex);
			}
		}

		public function checkLostSteps(positionIndex:int):void {
			_ballCollisions = 0;
			_cushionCollisions = 0;
			var addedVelocityMagnitude:Number = 0;
			if(animationLength > 0) {
				for(var i:int = _lastStepIndex + 1;i <= positionIndex;i++) {
					var currStep:AnimationStep = _animationPath[i];
					if(currStep.collided) {
						for(var j in currStep.collidedParticles) {
							if(currStep.collidedParticles[j] is Ball) {
								var collidedBall:Ball = currStep.collidedParticles[j];
								// to make sure that a ball ball collision is tracked only ones
								if(collidedBall.id < id) {
									if(!(currStep.isStopped && collidedBall.step.isStopped)){
										_ballCollisions++;
										addedVelocityMagnitude += _step.velocity.magnitude();
										addedVelocityMagnitude += collidedBall.step.velocity.magnitude();	
									}
								}
							}else{
								_cushionCollisions++;
							}
						}
					}
				}
			}
			_lastStepIndex = positionIndex;
			_collisionVelocityMagnitude = addedVelocityMagnitude/_ballCollisions;
		}
		
		public function get firstBallTouched():Ball{
			return _firstBallTouched;
		}
		
		public function get touchedCushion():Boolean{
			return _touchedCushion;
		}

		public function get ballCollisions():int{
			return _ballCollisions;
		}
		
		public function get cushionCollisions():int{
			return _cushionCollisions;
		}
		
		public function get collisionVelocityMagnitude():Number{
			return _collisionVelocityMagnitude;
		}

		public function get animationLength():uint{
			return _animationPath.length;
		}
		
		public function get animationPath():Array{
			return _animationPath;
		}

		public function deletePath():void {
			_animationPath.length = 0 ;
		}
		
		public function clearRuleInfo():void{
			_firstBallTouched = null;
			_touchedCushion = false;
		}
				
		override public function resolveCollision( mtd:Vector, velocity:Vector, normal:Vector, depth:Number,
												   order:int, otherParticle:AbstractParticle, vnBefore:Vector, vtBefore:Vector):void {

			if(otherParticle is Ball && _firstBallTouched == null){
				_firstBallTouched = Ball(otherParticle);
			}
			if(otherParticle is RectangleParticle && !_touchedCushion && _firstBallTouched != null){
				_touchedCushion = true;
			}
			
			super.resolveCollision(mtd, velocity, normal, depth, order, otherParticle, vnBefore, vtBefore);			
		}
		
		/**********************************************
		 * 					SETTINGS				  *
		 **********************************************/
		
		private function settingChanged(event:SettingEvent):void {
			switch(event.key) {			
				case BillardSettings.KEY_MAX_SPEED: 
					if(id == 0){
						Debug.trace("settingChanged "+settings.getValue(BillardSettings.KEY_MAX_SPEED));
					}
					maxSpeed = settings.getValue(BillardSettings.KEY_MAX_SPEED);		
				case BillardSettings.KEY_MAX_SPIN: 
					maxSpin = settings.getValue(BillardSettings.KEY_MAX_SPIN);		
				case BillardSettings.KEY_IMPULSE_SIGN: 
					impulseSign = settings.getValue(BillardSettings.KEY_IMPULSE_SIGN);break; 			
				case BillardSettings.KEY_IMPULSE_WEIGHT_SELF: 
					impulseWeightSelf = settings.getValue(BillardSettings.KEY_IMPULSE_WEIGHT_SELF);break; 			
				case BillardSettings.KEY_IMPULSE_WEIGHT_OTHER: 
					impulseWeightOther = settings.getValue(BillardSettings.KEY_IMPULSE_WEIGHT_OTHER);break; 
				case BillardSettings.KEY_SPIN_SHIFT_FACTOR: 
					spinShiftFactor = settings.getValue(BillardSettings.KEY_SPIN_SHIFT_FACTOR);break; 		
				case BillardSettings.KEY_SPIN_Z_TO_TRANSLATION_DIRECTION_CHANGE_FACTOR: 				
					spinZToTranslationalDirectionChangeFactor = settings.getValue(BillardSettings.KEY_SPIN_Z_TO_TRANSLATION_DIRECTION_CHANGE_FACTOR);break; 
				case BillardSettings.KEY_SPIN_IMPULSE_ON_CONTACT_FACTOR: 
					spinImpulseOnContactFactor = settings.getValue(BillardSettings.KEY_SPIN_IMPULSE_ON_CONTACT_FACTOR);break; 
				case BillardSettings.KEY_TRANSLATIONAL_FRICTION_EXP_XY: 
					translationalFrictionExpXY = settings.getValue(BillardSettings.KEY_TRANSLATIONAL_FRICTION_EXP_XY);break; 		
				case BillardSettings.KEY_TRANSLATIONAL_FRICTION_LIN_XY: 
					translationalFrictionLinXY = settings.getValue(BillardSettings.KEY_TRANSLATIONAL_FRICTION_LIN_XY);break; 	
				case BillardSettings.KEY_TRANSLATIONAL_FRICTION_CONST_XY: 
					translationalFrictionConstXY = settings.getValue(BillardSettings.KEY_TRANSLATIONAL_FRICTION_CONST_XY);break; 
				case BillardSettings.KEY_ROTATIONAL_FRICTION_EXP_Z: 
					rotationalFrictionExpZ = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_EXP_Z);break;		
				case BillardSettings.KEY_ROTATIONAL_FRICTION_LIN_Z: 
					rotationalFrictionLinZ = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_LIN_Z);break; 	
				case BillardSettings.KEY_ROTATIONAL_FRICTION_CONST_Z: 
					rotationalFrictionConstZ = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_CONST_Z);break; 
				case BillardSettings.KEY_ROTATIONAL_FRICTION_EXP_XY: 
					rotationalFrictionExpXY = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_EXP_XY);break; 
				case BillardSettings.KEY_ROTATIONAL_FRICTION_LIN_XY: 
					rotationalFrictionLinXY = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_LIN_XY);break; 
				case BillardSettings.KEY_ROTATIONAL_FRICTION_CONST_XY: 
					rotationalFrictionConstXY = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_CONST_XY);break;
				case BillardSettings.KEY_ROTATION_TO_TRANSLATION_CONVERSION_BY_ROLLING_EFFET_XY: 
					rotationToTranslationConversionByRollingEffetXY = settings.getValue(BillardSettings.KEY_ROTATION_TO_TRANSLATION_CONVERSION_BY_ROLLING_EFFET_XY);break; 
				case BillardSettings.KEY_SPIN_ANGLE_SHIFT_FACTOR: 
					rotationToTranslationConversionByRollingEffetXY = settings.getValue(BillardSettings.KEY_ROTATION_TO_TRANSLATION_CONVERSION_BY_ROLLING_EFFET_XY);break; 
			}
		}

		private function refreshSettings(event:SettingEvent):void {
			maxSpeed = settings.getValue(BillardSettings.KEY_MAX_SPEED);		
			maxSpin = settings.getValue(BillardSettings.KEY_MAX_SPIN);		
			impulseSign = settings.getValue(BillardSettings.KEY_IMPULSE_SIGN);			
			impulseWeightSelf = settings.getValue(BillardSettings.KEY_IMPULSE_WEIGHT_SELF);			
			impulseWeightOther = settings.getValue(BillardSettings.KEY_IMPULSE_WEIGHT_OTHER);
			spinShiftFactor = settings.getValue(BillardSettings.KEY_SPIN_SHIFT_FACTOR); 					
			spinZToTranslationalDirectionChangeFactor = settings.getValue(BillardSettings.KEY_SPIN_Z_TO_TRANSLATION_DIRECTION_CHANGE_FACTOR);
			spinImpulseOnContactFactor = settings.getValue(BillardSettings.KEY_SPIN_IMPULSE_ON_CONTACT_FACTOR);
			translationalFrictionExpXY = settings.getValue(BillardSettings.KEY_TRANSLATIONAL_FRICTION_EXP_XY); 		
			translationalFrictionLinXY = settings.getValue(BillardSettings.KEY_TRANSLATIONAL_FRICTION_LIN_XY);	
			translationalFrictionConstXY = settings.getValue(BillardSettings.KEY_TRANSLATIONAL_FRICTION_CONST_XY);
			rotationalFrictionExpZ = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_EXP_Z);		
			rotationalFrictionLinZ = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_LIN_Z); 	
			rotationalFrictionConstZ = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_CONST_Z); 
			rotationalFrictionExpXY = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_EXP_XY); 
			rotationalFrictionLinXY = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_LIN_XY);
			rotationalFrictionConstXY = settings.getValue(BillardSettings.KEY_ROTATIONAL_FRICTION_CONST_XY);
			rotationToTranslationConversionByRollingEffetXY = settings.getValue(BillardSettings.KEY_ROTATION_TO_TRANSLATION_CONVERSION_BY_ROLLING_EFFET_XY); 
			rotationToTranslationConversionByRollingEffetXY = settings.getValue(BillardSettings.KEY_ROTATION_TO_TRANSLATION_CONVERSION_BY_ROLLING_EFFET_XY); 
		}
		
	}
}
